package modelo;

import java.util.ArrayList;
import java.util.PriorityQueue;

import vista.Janela;

import controle.Simulator;

import extra.Constantes;
import extra.Gerador;

public class Host {
	//Um identificador unico para cada host
	private String identificador;
	//A distancia em metros do host ao comutador
	private int distancia;
	//O tempo de transmissao de 1 bit em milisegundos
	private double tempoTransmissao;
	//O tempo medio entre chegadas de mensagens a serem transmitidas
    private double tempoMedioEntreChegadas;
    //Flag que indica se o tempo entre chegadas deve ser exponencialmente distribuido ou deterministico
    private boolean chegadaExponencial;
    //O numero de medio de quadros por mensagem
    private double numeroMedioQuadros;
    //Esta variavel guarda o numero de quadros que chegou em cada mensagem que ainda nao foi transmitida por completo 
    private ArrayList<Integer> quadrosMensagens;
    //Esta variavel armazena o numero de quadros que foram transmitidos com sucesso da mensagem que esta sendo transmitida neste momento
    private int quadrosTransmitidosMensagem;
    //Flag que define se um host esta executando transmissao
    private boolean transmitindo;
    //Flag que define se um host esta executando recepcao
    private boolean recebendo;
    //Flag que define se um host esta executando transmissao de reforco
    private boolean transmitindoReforco;
    //Flag que define se um host esta executando recepcao de reforco
    private boolean recebendoReforco;
    //O instante de tempo onde ocorreu o fim da ultima recepcao
    private double fimUltimaRecepcao;
    //O ID da ultima transmissao que comecou a ser transmitida, mas ainda nao foi escutada sem colisao
    private int idUltimaTransmissaoNaoConfirmada;
    //O Evento que marca a ultima transmissao nao confirmada
    private Evento ultimaTransmissaoNaoConfirmada;
    //Numero de recepcoes de quadros em colisao que foram identificadas pelo host
    private int numeroRecepcoesQuadrosEmColisao;
    //Lista de eventos de transmissao pendentes
    private PriorityQueue<Evento> listaTransmissoesPendentes;
    private PriorityQueue<Evento> listaEventosColisao;
    
    //ATRIBUTOS UTILIZADOS PARA CALCULO DAS ESTATISTICAS PEDIDAS
    //Guarda o tempo da primeira tentativa de transmissao ainda nao concluida com sucesso
    private double tempoPrimeiraTentativaTransmissaoTAP;
    //Guarda o total de quadros considerados pelo TAP
    private int numeroQuadrosTransmitidosTAP;
    //Acumulador de tempos TAP
    private double totalTAP;
    
    //Guarda o tempo da primeira tentativa de transmissao de uma mensagem ainda nao concluida com sucesso
    private double tempoPrimeiraTentativaTransmissaoTAM;
    //Guarda o total de mensagem considerados pelo TAM
    private int numeroMensagensTransmitidasTAM;
    //Acumulador de tempos TAM
    private double totalTAM;
    
    //NCM
    //Numero de colisoes da mensagem sendo transmitida ate o momento
    private int numeroColisoesMensagemNCM;
    //Numero de mensagens transmitidas no total
    private int numeroMensagensTransmitidasNCM;
    //Soma das medias de colisar a cada mensagem
    private double somaMediasColisoesMensagemNCM;
    
    //Utilizacao
    //Tempo total que o host sentiu o meio ocupado
    private double tempoTotalMeioOcupadoUtilizacao;
    //Tempo em que a ultima recepcao comecou
    private double tempoInicioUltimaRecepcaoUtilizacao;
    
    //Vazao
    private int numeroTotalQuadrosTransmitidosVazao;
    
    public Host (String identificador, int distancia, double tempoTransmissao, double tempoMedioEntreChegadas, boolean chegadaExponencial, double numeroMedioQuadros) {
    	this.setIdentificador(identificador);
    	this.setDistancia(distancia);
    	this.setTempoTransmissao(tempoTransmissao);
    	this.setTempoMedioEntreChegadas(tempoMedioEntreChegadas);
    	this.setChegadaExponencial(chegadaExponencial);
    	this.setNumeroMedioQuadros(numeroMedioQuadros);
    	this.setQuadrosMensagens(new ArrayList<Integer>());
    	this.setQuadrosTransmitidosMensagem(0);
    	this.setFimUltimaRecepcao(0.0);
    	this.setIdUltimaTransmissaoNaoConfirmada(-1);
    	this.setUltimaTransmissaoNaoConfirmada(null);
    	this.setNumeroRecepcoesQuadrosEmColisao(0);
    	this.setRecebendo(false);
    	this.setTransmitindo(false);
    	this.setRecebendoReforco(false);
    	this.setTransmitindoReforco(false);
    	this.setListaTransmissoesPendentes(new PriorityQueue<Evento>());
    	this.setListaEventosColisao(new PriorityQueue<Evento>());
    	//TAP
    	this.setTempoPrimeiraTentativaTransmissaoTAP(-1);
    	this.setNumeroQuadrosTransmitidosTAP(0);
    	this.setTotalTAP(0);
    	//TAM
    	this.setTempoPrimeiraTentativaTransmissaoTAM(0);
    	this.setNumeroMensagensTransmitidasTAM(0);
    	this.setTotalTAM(0);
    	//NCm
    	this.setNumeroColisoesMensagemNCM(0);
    	this.setNumeroMensagensTransmitidasNCM(0);
    	this.setSomaMediasColisoesMensagemNCM(0);
    	
    	//Utilizacao
    	this.setTempoInicioUltimaRecepcaoUtilizacao(0);
    	this.setTempoTotalMeioOcupadoUtilizacao(0);
    	
    	//Vazao
    	this.setNumeroTotalQuadrosTransmitidosVazao(0);
    }
    
    public int getNumeroQuadrosMensagem() {
    	if (this.getNumeroMedioQuadros() < 1) {
    		return Gerador.geraAmostraGeometrica(this.getNumeroMedioQuadros());
    	}
    	else {
    		return (int) Math.round(this.getNumeroMedioQuadros());
    	}
    }
    
    public double getTempoProximaChegada(double tempoAtual) {
    	if (this.isChegadaExponencial()) {
    		return Gerador.geraAmostraExponencial(this.getTempoMedioEntreChegadas()) + tempoAtual;
    	}
    	else {
    		return this.getTempoMedioEntreChegadas() + tempoAtual;
    	}
    }

	public String getIdentificador() {
		return identificador;
	}

	public void setIdentificador(String identificador) {
		this.identificador = identificador;
	}

	public int getDistancia() {
		return distancia;
	}

	public void setDistancia(int distancia) {
		this.distancia = distancia;
	}
	
	public double getTempoTransmissaoQuadro() {
		return this.getTempoTransmissao() * Constantes.TAMANHO_QUADRO;
	}
	
	public double getTempoTransmissaoReforco() {
		return this.getTempoTransmissao() * Constantes.TAMANHO_QUADRO_REFORCO;
	}

	public double getTempoTransmissao() {
		return tempoTransmissao;
	}

	public void setTempoTransmissao(double tempoTransmissao) {
		this.tempoTransmissao = tempoTransmissao;
	}

	public double getTempoMedioEntreChegadas() {
		return tempoMedioEntreChegadas;
	}

	public void setTempoMedioEntreChegadas(double tempoMedioEntreChegadas) {
		this.tempoMedioEntreChegadas = tempoMedioEntreChegadas;
	}

	public boolean isChegadaExponencial() {
		return chegadaExponencial;
	}

	public void setChegadaExponencial(boolean chegadaExponencial) {
		this.chegadaExponencial = chegadaExponencial;
	}

	public double getNumeroMedioQuadros() {
		return numeroMedioQuadros;
	}

	public void setNumeroMedioQuadros(double numeroMedioQuadros) {
		this.numeroMedioQuadros = numeroMedioQuadros;
	}
	
	public ArrayList<Integer> getQuadrosMensagens() {
		return quadrosMensagens;
	}

	public void setQuadrosMensagens(ArrayList<Integer> quadrosMensagens) {
		this.quadrosMensagens = quadrosMensagens;
	}
	
	public int getQuadrosTransmitidosMensagem() {
		return quadrosTransmitidosMensagem;
	}

	public void setQuadrosTransmitidosMensagem(int quadrosTransmitidosMensagem) {
		this.quadrosTransmitidosMensagem = quadrosTransmitidosMensagem;
	}
	
	public void adicionaQuadroTransmitidoMensagem(double tempoEventoPai) {
		if (!Simulator.isFaseTransiente()) {
			//Aumenta o numero total de quadros transmitidos com sucesso pela estacao
			this.setNumeroTotalQuadrosTransmitidosVazao(this.getNumeroTotalQuadrosTransmitidosVazao() + 1);
		}
		try {
			if ((this.getQuadrosTransmitidosMensagem() + 1) == this.getQuadrosMensagens().get(0)) {
				//Acabou de transmitir todos os quadros da mensagem
				
				if (!Simulator.isFaseTransiente()) {
					//Transmitiu mais uma mensagem por completo
					this.setNumeroMensagensTransmitidasNCM(this.getNumeroMensagensTransmitidasNCM() + 1);
					//Acumula a media de colisoes que ocorreu nesta mensagem
					this.setSomaMediasColisoesMensagemNCM(this.getSomaMediasColisoesMensagemNCM() + (this.getNumeroColisoesMensagemNCM() / this.getQuadrosMensagens().get(0)));
					//Reseta o numero de colisoes que ocorreram durante a transmissao da mensagem
					this.setNumeroColisoesMensagemNCM(0);
				}
				
				this.removeQuadrosMensagens(0);
				this.setQuadrosTransmitidosMensagem(0);
				
				if (this.getTempoPrimeiraTentativaTransmissaoTAM() == -1) {
					this.setTempoPrimeiraTentativaTransmissaoTAM(0);
				}
				else {
					if (!Simulator.isFaseTransiente()) {
						this.setNumeroMensagensTransmitidasTAM(this.getNumeroMensagensTransmitidasTAM() + 1);
						this.setTotalTAM(this.getTotalTAM() + (tempoEventoPai - this.getTempoPrimeiraTentativaTransmissaoTAM()));
						this.setTempoPrimeiraTentativaTransmissaoTAM(0);
					}
				}
			}
			else {
				this.setQuadrosTransmitidosMensagem(this.getQuadrosTransmitidosMensagem() + 1);
			}
		}catch (Exception e) {
			Janela.imprime("Tente Novamente até conseguir o resultado Correto.Houve um erro na ordem de Processamento dos Eventos!");
		}
	}
	
	public void removeQuadrosMensagens(int indice) {
		this.quadrosMensagens.remove(indice);
	}

	public void adicionaQuadrosMensagem(Integer numeroQuadros) {
		this.quadrosMensagens.add(numeroQuadros);
	}
	
	public boolean isTransmitindo() {
		return transmitindo;
	}

	public void setTransmitindo(boolean transmitindo) {
		this.transmitindo = transmitindo;
	}

	public boolean isRecebendo() {
		return recebendo;
	}

	public void setRecebendo(boolean recebendo) {
		if (recebendo) {
			this.setTempoInicioUltimaRecepcaoUtilizacao(Simulator.getTempoAtual());
		}
		else {
			if (!Simulator.isFaseTransiente()) {
				this.setTempoTotalMeioOcupadoUtilizacao(this.getTempoTotalMeioOcupadoUtilizacao() + (Simulator.getTempoAtual() - this.getTempoInicioUltimaRecepcaoUtilizacao()));
			}
			this.setTempoInicioUltimaRecepcaoUtilizacao(0);
		}
		this.recebendo = recebendo;
	}
	
	public boolean isTransmitindoReforco() {
		return transmitindoReforco;
	}

	public void setTransmitindoReforco(boolean transmitindoReforco) {
		this.transmitindoReforco = transmitindoReforco;
	}

	public boolean isRecebendoReforco() {
		return recebendoReforco;
	}

	public void setRecebendoReforco(boolean recebendoReforco) {
		this.recebendoReforco = recebendoReforco;
	}

	public double getFimUltimaRecepcao() {
		return fimUltimaRecepcao;
	}

	public void setFimUltimaRecepcao(double fimUltimaRecepcao) {
		this.fimUltimaRecepcao = fimUltimaRecepcao;
	}

	public PriorityQueue<Evento> getListaTransmissoesPendentes() {
		return listaTransmissoesPendentes;
	}

	public void setListaTransmissoesPendentes(PriorityQueue<Evento> listaTransmissoesPendentes) {
		this.listaTransmissoesPendentes = listaTransmissoesPendentes;
	}
	
	public void adicionaTransmissaoPendente(Evento transmissaoPendente) {
		this.listaTransmissoesPendentes.add(transmissaoPendente);
	}

	public int getIdUltimaTransmissaoNaoConfirmada() {
		return idUltimaTransmissaoNaoConfirmada;
	}

	public void setIdUltimaTransmissaoNaoConfirmada(int idUltimaTransmissaoNaoConfirmada) {
		this.idUltimaTransmissaoNaoConfirmada = idUltimaTransmissaoNaoConfirmada;
	}

	public int getNumeroRecepcoesQuadrosEmColisao() {
		return numeroRecepcoesQuadrosEmColisao;
	}

	public void setNumeroRecepcoesQuadrosEmColisao(
			int numeroRecepcoesQuadrosEmColisao) {
		this.numeroRecepcoesQuadrosEmColisao = numeroRecepcoesQuadrosEmColisao;
	}

	@Override
	public String toString() {
		return "HOST " + this.getIdentificador();
	}

	public boolean podeTransmitir() {
		return false;
	}

	public double getTempoPropagacao() {
		return this.getDistancia() * Constantes.ATRASO_PROPAGACAO;
	}

	public Evento getProximaTransmissaoPendente() {
		return this.listaTransmissoesPendentes.poll();
	}
	
	public boolean hasTransmissaoAgendada() {
		for (Evento evento : Simulator.getListaEventos()) {
			if (evento.getHost() == this && (evento.getTipo() == Evento.tipos.InicioTransmissaoQuadro || evento.getTipo() == Evento.tipos.InicioTransmissaoReforco)) {
				return true;
			}
		}
		
		return false;
	}

	public PriorityQueue<Evento> getListaEventosColisao() {
		return listaEventosColisao;
	}

	public void setListaEventosColisao(PriorityQueue<Evento> listaEventosColisao) {
		this.listaEventosColisao = listaEventosColisao;
	}

	public void adicionaEventoColisao(Evento evento) {
		this.listaEventosColisao.add(evento);
	}
	
	public Evento getProximaTransmissaoBackoff() {
		return this.listaEventosColisao.poll();
	}

	public Evento getUltimaTransmissaoNaoConfirmada() {
		return ultimaTransmissaoNaoConfirmada;
	}

	public void setUltimaTransmissaoNaoConfirmada(
			Evento ultimaTransmissaoNaoConfirmada) {
		this.ultimaTransmissaoNaoConfirmada = ultimaTransmissaoNaoConfirmada;
	}

	public double getTempoPrimeiraTentativaTransmissaoTAP() {
		return tempoPrimeiraTentativaTransmissaoTAP;
	}

	public void setTempoPrimeiraTentativaTransmissaoTAP(double tempoPrimeiraTentativaTransmissaoTAP) {
		this.tempoPrimeiraTentativaTransmissaoTAP = tempoPrimeiraTentativaTransmissaoTAP;
	}

	public int getNumeroQuadrosTransmitidosTAP() {
		return numeroQuadrosTransmitidosTAP;
	}

	public void setNumeroQuadrosTransmitidosTAP(int numeroQuadrosTransmitidosTAP) {
		this.numeroQuadrosTransmitidosTAP = numeroQuadrosTransmitidosTAP;
	}

	public double getTotalTAP() {
		return totalTAP;
	}

	public void setTotalTAP(double totalTAP) {
		this.totalTAP = totalTAP;
	}

	public double getTempoPrimeiraTentativaTransmissaoTAM() {
		return tempoPrimeiraTentativaTransmissaoTAM;
	}

	public void setTempoPrimeiraTentativaTransmissaoTAM(double tempoPrimeiraTentativaTransmissaoTAM) {
		this.tempoPrimeiraTentativaTransmissaoTAM = tempoPrimeiraTentativaTransmissaoTAM;
	}

	public int getNumeroMensagensTransmitidasTAM() {
		return numeroMensagensTransmitidasTAM;
	}

	public void setNumeroMensagensTransmitidasTAM(int numeroMensagensTransmitidasTAM) {
		this.numeroMensagensTransmitidasTAM = numeroMensagensTransmitidasTAM;
	}

	public double getTotalTAM() {
		return totalTAM;
	}

	public void setTotalTAM(double totalTAM) {
		this.totalTAM = totalTAM;
	}

	public int getNumeroColisoesMensagemNCM() {
		return numeroColisoesMensagemNCM;
	}

	public void setNumeroColisoesMensagemNCM(int numeroColisoesMensagemNCM) {
		this.numeroColisoesMensagemNCM = numeroColisoesMensagemNCM;
	}

	public int getNumeroMensagensTransmitidasNCM() {
		return numeroMensagensTransmitidasNCM;
	}

	public void setNumeroMensagensTransmitidasNCM(int numeroMensagensTransmitidasNCM) {
		this.numeroMensagensTransmitidasNCM = numeroMensagensTransmitidasNCM;
	}

	public double getSomaMediasColisoesMensagemNCM() {
		return somaMediasColisoesMensagemNCM;
	}

	public void setSomaMediasColisoesMensagemNCM(
			double somaMediasColisoesMensagemNCM) {
		this.somaMediasColisoesMensagemNCM = somaMediasColisoesMensagemNCM;
	}

	public double getTempoTotalMeioOcupadoUtilizacao() {
		return tempoTotalMeioOcupadoUtilizacao;
	}

	public void setTempoTotalMeioOcupadoUtilizacao(
			double tempoTotalMeioOcupadoUtilizacao) {
		this.tempoTotalMeioOcupadoUtilizacao = tempoTotalMeioOcupadoUtilizacao;
	}

	public double getTempoInicioUltimaRecepcaoUtilizacao() {
		return tempoInicioUltimaRecepcaoUtilizacao;
	}

	public void setTempoInicioUltimaRecepcaoUtilizacao(
			double tempoInicioUltimaRecepcaoUtilizacao) {
		this.tempoInicioUltimaRecepcaoUtilizacao = tempoInicioUltimaRecepcaoUtilizacao;
	}

	public int getNumeroTotalQuadrosTransmitidosVazao() {
		return numeroTotalQuadrosTransmitidosVazao;
	}

	public void setNumeroTotalQuadrosTransmitidosVazao(
			int numeroTotalQuadrosTransmitidosVazao) {
		this.numeroTotalQuadrosTransmitidosVazao = numeroTotalQuadrosTransmitidosVazao;
	}
}
